/*
 *  Copright: 2008 Philipp Leitner, Distributed Systems Group,
 *            TU Wien, leitner@infosys.tuwien.ac.at
 *
 *  This file is part of DAIOS.
 *
 *  DAIOS is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as
 *  published by the Free Software Foundation.
 * 
 *  DAIOS is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with DAIOS.  If not, see
 *  <http://www.gnu.org/licenses/>.
 * 
 */

package at.ac.tuwien.infosys.dsg.daiosPlugins.nativeInvoker.backend;

import java.io.InputStream;
import java.util.Hashtable;
import java.util.List;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.om.OMNamespace;
import org.apache.axiom.om.OMText;
import org.apache.axiom.om.impl.llom.OMSourcedElementImpl;
import org.apache.axiom.om.xpath.AXIOMXPath;
import org.jaxen.JaxenException;
import org.jaxen.XPath;

import at.ac.tuwien.infosys.dsg.daios.exceptions.SOAPEncodedException;
import at.ac.tuwien.infosys.dsg.daios.framework.interfaces.IConstants;
import at.ac.tuwien.infosys.dsg.daiosPlugins.axis2.axiom.OMUtil;

/**
 * A utility class that implements SOAP en- and decoding.
 * 
 * @author philipp
 *
 */
public class SOAPEncoder {
	
	// the XPath expression to the body of a SOAP message 
	private static final String SOAP_BODY_PATH =
		"//*[local-name(.) = 'Body' and " +
		   "namespace-uri(.) = 'http://schemas.xmlsoap.org/soap/envelope/']/* ";

	// the XPath expression to the header of a SOAP message 
	private static final String SOAP_HEAD_PATH =
		"//*[local-name(.) = 'Header' and " +
		   "namespace-uri(.) = 'http://schemas.xmlsoap.org/soap/envelope/']/* ";	

	// the XPath expression to the fault element of a SOAP fault message
	private static final String SOAP_FAULT_PATH =
		"//*[local-name(.) = 'Fault']";
	
	// the XPath expression to the multirefs of a SOAP RPC invocation result
	private static final String MULTIREF_PATH =
		"//*[local-name(.) = 'multiRef']";
	
	private OMFactory fac = null;
	
	/**
	 * Private constructor. Use SOAPEncoder.getInstance() to create new
	 * encoder instances.
	 *
	 */
	private SOAPEncoder() {
		fac = OMAbstractFactory.getOMFactory();
	}
	
	/**
	 * Factory method. Construct a new SOAPEncoder instance.
	 * 
	 * @return The freshly constructed encoder.
	 */
	protected static SOAPEncoder getInstance() {
		return new SOAPEncoder();
	}
	
	/**
	 * SOAP-encode the body (i.e. add SOAP envelope) and add
	 * a list of user-defined SOAP request headers.
	 * 
	 * @param body The XML body.
	 * @param headers A table of user-defined SOAP headers to
	 * add to the SOAP message.
	 * @return The SOAP message (in XML representation).
	 */
	protected OMElement soapEncode(OMElement body,
			Hashtable<QName, String> headers) {
		
		// write header
		OMElement header = 
			writeHeader(headers);
		
		// write body
		OMNamespace soapenv = fac.createOMNamespace(
				IConstants.SOAPENV_NS, "soapenc");
		OMElement bodyEnv =
			fac.createOMElement("Body", soapenv);
		bodyEnv.addChild(body);
		
		// merge header and body to create full SOAP message
		OMElement soapMsg = 
			fac.createOMElement("Envelope", soapenv);		
		soapMsg.addChild(header);
		soapMsg.addChild(bodyEnv);
		
		return soapMsg;
		
	}
	
	/**
	 * SOAP-decode a message in XML representation.
	 * 
	 * @param encoded The SOAP message as String.
	 * @param httpHeaders The HTTP response headers to
	 * set in the SOAPResponse.
	 * @return The SOAPResponse containing the SOAP response
	 * body, a table of SOAP response headers and another
	 * table of HTTP response headers.
	 * 
	 * @throws SOAPEncodedException
	 * @throws JaxenException
	 * @throws XMLStreamException
	 */
	protected SOAPResponse soapDecode(InputStream encoded,
			Hashtable<String, String> httpHeaders)
				throws SOAPEncodedException, XMLStreamException, JaxenException {
		
		XPath xPathExpression = null;
		
		// convert to AXIOM model
		OMElement omEncoded = new OMSourcedElementImpl(
			new QName(IConstants.SOAPENV_NS,"Envelope"), fac,
			new InputStreamDataSource(encoded));
		
		// check for fault
		OMElement fault = getFaultNode(omEncoded);
		if(fault != null)
			throw new SOAPEncodedException(fault);
		
		// go to SOAP body
		OMElement body = null;
		xPathExpression = new AXIOMXPath(SOAP_BODY_PATH);
		body = (OMElement) xPathExpression.selectSingleNode(omEncoded);
		
		// go to multirefs
		List<OMElement> multirefs = null;
		xPathExpression = new AXIOMXPath(MULTIREF_PATH);
		multirefs =
			(List<OMElement>) xPathExpression.evaluate(omEncoded);
		
		// go to soap headers
		List<OMElement> headers = null;
		xPathExpression = new AXIOMXPath(SOAP_HEAD_PATH);
		headers =
			(List<OMElement>) xPathExpression.evaluate(omEncoded);
		
		// build response and return
		return new SOAPResponse(body, multirefs, httpHeaders,
				parseHeaders(headers));
	}

	/**
	 * Get the 'fault' node from a SOAP document. If this is
	 * no SOAP fault message this method will return null.
	 * 
	 * @param document A SOAP message.
	 * @return A Fault element if this is a Fault message,
	 * null otherwise.
	 * @throws JaxenException
	 */
	private OMElement getFaultNode(OMElement document)
		throws JaxenException {
		
		
		XPath xPath = null;
		OMElement fault = null;
		xPath = new AXIOMXPath(SOAP_FAULT_PATH);
		fault =
			(OMElement) xPath.selectSingleNode(document);
			
		return fault;
		
	}
	
	/**
	 * Takes a list of header elements from a SOAP message
	 * and puts them into a Hashtable.
	 * 
	 * @param nodes A list of header elements.
	 * @return A table containing all headers from the list.
	 */
	private Hashtable<QName, String> parseHeaders(
			List<OMElement> nodes) {
		
		Hashtable<QName, String> headers = new 
			Hashtable<QName, String>();
		
		if(nodes == null)
			return headers;
		
		for(OMElement n : nodes) {
			headers.put(n.getQName(), n.getText());
		}
		
		return headers;

	}
	
	/**
	 * Create a SOAP Header element that contains all the headers
	 * specified in a table (given as parameter).
	 * 
	 * @param headers All headers that should be contained in the
	 * SOPA Header element to construct.
	 * @return The SOAP Header in AXIOM representation.
	 */
	private OMElement writeHeader(Hashtable<QName, String> headers) {
		
		OMNamespace soapenv = fac.createOMNamespace(
				IConstants.SOAPENV_NS, "soapenv");
		OMElement env = fac.createOMElement("Header", soapenv);
		
		OMElement header = null;
		OMText headerVal = null;
		
		for(QName qname : headers.keySet()) {
			header = fac.createOMElement(qname);
			headerVal = fac.createOMText(headers.get(qname));
			header.addChild(headerVal);
			env.addChild(header);
		}
		
		return env;
		
	}
	
}
